package applog

import (
	"bytes"
	"context"
	"gitee.com/ymofen/gobase"
	"io"
	"log/slog"
	"strconv"
	"strings"
	"sync"
)

type KplTextHandler struct {
	lk         sync.RWMutex
	w          io.Writer
	suffix     bytes.Buffer
	groups     []string
	attrPrefix []byte
	opts       slog.HandlerOptions
}

func NewKplTextHandler(w io.Writer, opts *slog.HandlerOptions) *KplTextHandler {
	if opts == nil {
		opts = &slog.HandlerOptions{}
	}
	return &KplTextHandler{
		w:    w,
		opts: *opts,
	}
}

func (h *KplTextHandler) clone() *KplTextHandler {
	// We can't use assignment because we can't copy the mutex.
	return &KplTextHandler{
		opts: h.opts,
		w:    h.w,
		lk:   h.lk, // mutex shared among all clones of this handler
	}
}

func (h *KplTextHandler) WithGroup(name string) slog.Handler {
	h2 := h.clone()
	h2.groups = append(h2.groups, name)
	h2.attrPrefix = append([]byte(strings.Join(h2.groups, ".")), '.')
	return h2
}

func (h *KplTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
	h2 := h.clone()

	fn := GetReplaceAttrOrNull(&h2.opts)
	for _, attr := range attrs {
		a2 := fn(h.groups, attr)
		if h2.suffix.Len() > 0 {
			h2.suffix.WriteString(" ")
		}
		h2.suffix.WriteString(a2.String())
	}
	return h2
}

func (h *KplTextHandler) Enabled(ctx context.Context, l slog.Level) bool {
	minLevel := slog.LevelInfo
	if h.opts.Level != nil {
		minLevel = h.opts.Level.Level()
	}
	return l >= minLevel
}

func (h *KplTextHandler) Handle(ctx context.Context, r slog.Record) error {
	var bb bytes.Buffer
	bb.WriteString(gobase.DateTimeString(r.Time))
	bb.WriteString("\t")
	bb.WriteString(r.Level.String())

	if h.opts.AddSource {
		bb.WriteString("\t")
		ss := GetSource(r.PC)
		bb.WriteString(gobase.ExtractFileName(ss.File))
		bb.WriteString(":")
		bb.WriteString(strconv.Itoa(ss.Line))
	}

	bb.WriteString("\t")
	bb.WriteString(r.Message)
	if r.NumAttrs() > 0 {
		bb.WriteString("\t")
		fn := GetReplaceAttrOrNull(&h.opts)
		sn := 0
		r.Attrs(func(attr slog.Attr) bool {
			if sn > 0 {
				bb.WriteByte(' ')
			}

			a := fn(h.groups, attr)
			bb.Write(h.attrPrefix)
			bb.WriteString(a.String())
			sn++
			return true
		})
	}

	bb.WriteString("\n")
	h.lk.RLock()
	defer h.lk.RUnlock()
	_, err := h.w.Write(bb.Bytes())
	return err
}
